﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using ScaffoldIt.Configuration;

namespace ScaffoldIt
{
    static class TreeViewHelpers
    {
        public static bool Any<T>(this IEnumerable<TreeNode> nodes, Predicate<T> predicate) where T : class
        {
            foreach (TreeNode node in nodes)
            {
                T data = node.Tag as T;
                if (data != null && predicate(data))
                    return true;
            }

            return false;
        }

        public static bool Any<T>(this TreeNodeCollection nodes, Predicate<T> predicate) where T : class
        {
            foreach (TreeNode node in nodes)
            {
                T data = node.Tag as T;
                if (data != null && predicate(data)) 
                    return true;
            }

            return false;
        }

        public static TreeNode Find<T>(this TreeNodeCollection nodes, Predicate<T> predicate) where T : class
        {
            foreach (TreeNode node in nodes)
            {
                T data = node.Tag as T;
                if (data != null && predicate(data))
                    return node;
            }

            return null;
        }

        public static void SyncParameter(string refCode, Parameter parameter, TreeNodeCollection nodes, Action<TreeNode, TreeNode, Template, Parameter> callback)
        {
            foreach (TreeNode treeNode in nodes)
            {
                if (treeNode == null) continue;

                if (treeNode.Tag is Parameter)
                {
                    var nodeparameter = treeNode.Tag as Parameter;
                    var nodeTemplate = treeNode.Parent.Tag as Template;
                    if (nodeparameter.Code == refCode)
                    {
                        callback(treeNode.Parent, treeNode, nodeTemplate, nodeparameter);
                        nodeparameter.Code = parameter.Code;                        
                    }
                }
                else
                {
                    SyncParameter(refCode, parameter, treeNode.Nodes, callback);
                }
            }
        }

        public static void SyncCategory(string refName, TreeNode nodeCat, Category refcategory, TreeView treePresets, List<Preset> presets, Action<TreeNode, TreeNode, PresetCategory, PresetCategory> callback)
        {
            SyncCategoryTree(nodeCat, treePresets, refName, callback);
            SyncCategoryPresets(presets, nodeCat, refName, refcategory, callback);
        }

        private static void SyncCategoryPresets(List<Preset> presets, TreeNode nodeCat, string refName, Category refcategory, Action<TreeNode, TreeNode, PresetCategory, PresetCategory> callback)
        {
            foreach (var preset in presets)
            {
                var nodes = GetNodesStack(nodeCat);
                IEnumerable<PresetCategory> parent = preset.Categories;
                PresetCategory parentCat = null;

                while (nodes.Count > 0)
                {
                    var node = nodes.Pop();
                    if (node == nodeCat)
                    {
                        var current = parent.FirstOrDefault(c => c.Category == refName);
                        if (current == null) return;

                        callback(null, null, parentCat, current);
                    }
                    else if (node.Tag is Category)
                    {
                        var category = node.Tag as Category;
                        parentCat = parent.FirstOrDefault(c => c.Category == category.Name);
                        if (parentCat == null) return;
                        parent = parentCat.Categories;
                    }
                }
            }
        }
        private static void SyncCategoryTree(TreeNode nodeCat, TreeView treePresets, string refName, Action<TreeNode, TreeNode, PresetCategory, PresetCategory> callback)
        {
            var nodes = GetNodesStack(nodeCat);
            var rootNodes = treePresets.Nodes;
            TreeNode parent = null;

            while (nodes.Count > 0)
            {
                var node = nodes.Pop();
                if (node == nodeCat)
                {
                    var current = rootNodes.Find<PresetCategory>(c => c.Category == refName);
                    if (current == null) return;

                    PresetCategory currentCat = current.Tag as PresetCategory;
                    PresetCategory parentCat = (parent != null) ? parent.Tag as PresetCategory : null;

                    callback(parent, current, parentCat, currentCat);
                }
                else if (node.Tag is Category)
                {
                    var category = node.Tag as Category;
                    parent = rootNodes.Find<PresetCategory>(c => c.Category == category.Name);  
                    if (parent == null) return;
                    rootNodes = parent.Nodes;
                }
            }
        }

        public static void SyncTemplate(string refName, TreeNode nodeTempl, Template template, TreeView treePresets, List<Preset> presets, Action<TreeNode, TreeNode, PresetCategory, string> callback)
        {
            SyncTemplateTree(nodeTempl, treePresets, refName, callback);
            SyncTemplatePresets(nodeTempl, presets, template, refName, callback);
        }

        private static void SyncTemplateTree(TreeNode nodeTempl, TreeView treePresets, string refName, Action<TreeNode, TreeNode, PresetCategory, string> callback)
        {
            var nodes = GetNodesStack(nodeTempl);
            var rootNodes = treePresets.Nodes;
            TreeNode parent = null;

            while (nodes.Count > 0)
            {
                var node = nodes.Pop();
                if (node == nodeTempl)
                {
                    var current = rootNodes.Find<string>(c => c == refName);
                    if (current == null) return;

                    var currentTempl = current.Tag as string;
                    PresetCategory parentCat = (parent != null) ? parent.Tag as PresetCategory : null;

                    callback(parent, current, parentCat, currentTempl);
                }
                else if (node.Tag is Category)
                {
                    var category = node.Tag as Category;
                    parent = rootNodes.Find<PresetCategory>(c => c.Category == category.Name);
                    if (parent == null) return;
                    rootNodes = parent.Nodes;
                }
            }
        }

        private static void SyncTemplatePresets(TreeNode nodeTempl, List<Preset> presets, Template templ, string refName, Action<TreeNode, TreeNode, PresetCategory, string> callback)
        {
            foreach (var preset in presets)
            {
                var nodes = GetNodesStack(nodeTempl);
                var roots = preset.Categories;
                PresetCategory parent = null;

                while (nodes.Count > 0)
                {
                    var node = nodes.Pop();
                    if (node == nodeTempl)
                    {
                        callback(null, null, parent, templ.Name);
                    }
                    else if (node.Tag is Category)
                    {
                        var category = node.Tag as Category;
                        parent = roots.FirstOrDefault(c => c.Category == category.Name);
                        if (parent == null) return;
                        roots = parent.Categories;
                    }
                }
            }
        }

        private static IEnumerable<TreeNode> Expand(this IEnumerable<TreeNode> nodesToAdd)
        {
            foreach (var treeNode in nodesToAdd)
            {
                if (treeNode.Nodes.Count > 0)
                {
                    foreach (var node in treeNode.Nodes.Cast<TreeNode>().Expand())
                    {
                        yield return node;
                    }
                }
                else 
                    yield return treeNode;
            }
        }


        public static bool Sync(Preset preset, TreeView treePresets, List<TreeNode> nodesToAdd)
        {

            foreach (var treeNode in nodesToAdd.Expand())
            {
                var nodes = GetNodesStack(treeNode);
                var rootNodes = treePresets.Nodes;
                PresetCategory parent = null;

                while (nodes.Count > 0)
                {
                    var node = nodes.Pop();
                    if (node.Tag is Category)
                    {
                        var category = node.Tag as Category;
                        var catNode = rootNodes.Find<PresetCategory>(c => c.Category == category.Name);
                        if (catNode == null)
                        {
                            catNode = new TreeNode(category.Name);
                            var presetCat = new PresetCategory() { Category = category.Name };
                            catNode.Tag = presetCat;
                            rootNodes.Add(catNode);
                            if (parent == null)
                                preset.Categories.Add(presetCat);
                            else
                                parent.Categories.Add(presetCat);
                        }
                        parent = catNode.Tag as PresetCategory;
                        rootNodes = catNode.Nodes;
                    }
                    else if (node.Tag is Template)
                    {
                        var template = node.Tag as Template;
                        var templNode = rootNodes.Find<string>(t => t == template.Name);

                        if (templNode == null)
                        {
                            templNode = new TreeNode(template.Name);
                            templNode.ImageIndex = 1;
                            templNode.SelectedImageIndex = 1;
                            templNode.Tag = template.Name;
                            parent.AppendTemplate(template.Name);
                            rootNodes.Add(templNode);
                        }
                    }
                }
            }
            return true;
        }

        private static Stack<TreeNode> GetNodesStack(TreeNode treeNode)
        {
            Stack<TreeNode> nodes = new Stack<TreeNode>();
            var currNode = treeNode;
            nodes.Push(currNode);

            while (currNode.Parent != null)
            {
                nodes.Push(currNode.Parent);
                currNode = currNode.Parent;
            }

            return nodes;
        }
    }
}
